package gui.inventory;

import gui.common.Controller;
import gui.item.ItemData;
import gui.product.ProductData;
import hit.core.HomeInventoryTracker;
import hit.core.Item;
import hit.core.ItemFacade;
import hit.core.Product;
import hit.core.ProductContainer;
import hit.core.ProductFacade;
import hit.core.ProductGroup;
import hit.core.Size;
import hit.core.StorageUnit;
import hit.core.TreeFacade;
import hit.dao.DAOFactory;
import hit.dao.DataAccessObject;
import hit.dao.FactoryReference;
import hit.dao.serialization.SerializationDAOFactory;
import hit.util.DataConverter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * Controller class for inventory view.
 */
public class InventoryController extends Controller 
									implements IInventoryController {

	public TreeFacade treeFacade;
	public ProductFacade productFacade;
	public ItemFacade itemFacade;
	
	/**
	 * Constructor.
	 *  
	 * @param view Reference to the inventory view
	 */
	public InventoryController(IInventoryView view) {
		super(view);
				
		construct();
	}
	
	/**
	 * Returns a reference to the view for this controller.
	 */
	@Override
	protected IInventoryView getView() {
		return (IInventoryView)super.getView();
	}
	
	/**
	 * Sets the enable/disable state of all components in the controller's view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the controller's view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() {
		return;
	}

//---------------------------------------------------------------------------------------
	//Initialization Code
//---------------------------------------------------------------------------------------
	/**
	 * Loads data into the controller's view.
	 * 
	 *  {@pre None}
	 *  
	 *  {@post The controller has loaded data into its view}
	 */
	@Override
	protected void loadValues() {
		
		// Load the previous hit.
		DAOFactory factory = FactoryReference.GetFactory();
		HomeInventoryTracker loadedHit = factory.CreateDataAccessObject().Load();
		HomeInventoryTracker.LoadHit(loadedHit);
		
		//get the facades from the HomeInventoryTracker
		treeFacade = HomeInventoryTracker.Instance().GetTreeFacade();
		productFacade = HomeInventoryTracker.Instance().GetProductFacade();
		itemFacade = HomeInventoryTracker.Instance().GetItemFacade();
		
		//build the ProductContainer tree
		ProductContainerData root = BuildTree();
		getView().setProductContainers(root);
		
		//let this controller observe changes to the facade
		treeFacade.addObserver(new TreeObserver(getView(), root));
		productFacade.addObserver(new ProductObserver(getView(), this));
		itemFacade.addObserver(new ItemObserver(getView(), this));
	}
	
	/**
	 * Builds the tree from the root node on initialization
	 * */
	private ProductContainerData BuildTree()
	{
		ProductContainerData data_root = new ProductContainerData();
		
		Iterator<StorageUnit> storageIter = treeFacade.GetStorageUnitIterator();
		
		while (storageIter.hasNext())
		{
			StorageUnit temp = storageIter.next();

			data_root.addChild(BuildTreeNode(temp));
			
		}
		
		return data_root;
	}
	
	/**
	 * Recursively build the ProductContainer tree
	 * @param container the ProductContainer who's children should be added
	 * into the tree
	 * */
	private ProductContainerData BuildTreeNode(ProductContainer container)
	{
		ProductContainerData data_newNode = new ProductContainerData(container.GetName());
		data_newNode.setTag(container);	
		
		Iterator<ProductGroup> groupsIter = container.GetProductGroupIterator();
		
		while (groupsIter.hasNext())
		{
			ProductGroup temp = groupsIter.next();
			
			data_newNode.addChild(BuildTreeNode(temp));
		}
		
		return data_newNode;
	}
	
//---------------------------------------------------------------------------------------
	//Tree Methods
//---------------------------------------------------------------------------------------
	/**
	 * Returns true if and only if the "Add Storage Unit" menu item should be enabled.
	 * @return whether the Add Storage Unit menu item should be enabled
	 */
	@Override
	public boolean canAddStorageUnit() {
		return true;
	}
	
	/**
	 * This method is called when the user selects the "Add Storage Unit" menu item.
	 */
	@Override
	public void addStorageUnit() {
		getView().displayAddStorageUnitView();
		productContainerSelectionChanged();
	}
	
	/**
	 * Returns true if and only if the "Edit Storage Unit" menu item should be enabled.
	 */
	@Override
	public boolean canEditStorageUnit() {
		return true;
	}
	
	/**
	 * This method is called when the user selects the "Edit Storage Unit" menu item.
	 */
	@Override
	public void editStorageUnit() {
		getView().displayEditStorageUnitView();
	}
	
	/**
	 * Returns true if and only if the "Delete Storage Unit" menu item should be enabled.
	 * @return whether the Delete Storage Unit menu item should be enabled
	 */
	@Override
	public boolean canDeleteStorageUnit() {
		
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		
		if (selectedContainer == null ||
			selectedContainer.getTag() == null ||
			!(selectedContainer.getTag() instanceof StorageUnit))
			return false;
		
		StorageUnit unit = (StorageUnit)selectedContainer.getTag();
		
		return treeFacade.CanRemoveStorageUnit(unit);
	}
	
	/**
	 * This method is called when the user selects the "Delete Storage Unit" menu item.
	 */
	@Override
	public void deleteStorageUnit() {
		try {
			ProductContainerData selectedContainer = getView().getSelectedProductContainer();
			
			if (selectedContainer == null ||
					selectedContainer.getTag() == null ||
					!(selectedContainer.getTag() instanceof StorageUnit))
				getView().displayErrorMessage("Invalid Storage Unit");
			
			StorageUnit unit = (StorageUnit)selectedContainer.getTag();
			
			treeFacade.DeleteStorageUnit(unit);
			productContainerSelectionChanged();
			
			DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
			dao.StartTransaction();
			
			dao.GetStorageUnitDAO().Remove(unit);
			dao.EndTransaction(true);
			dao.CloseConnection();
		}
		catch (Exception e) {
			getView().displayErrorMessage("Failed to delete storage unit.");
		}
	}

	/**
	 * Returns true if and only if the "Add Product Group" menu item should be enabled.
	 */
	@Override
	public boolean canAddProductGroup() {
		return true;
	}
	
	/**
	 * This method is called when the user selects the "Add Product Group" menu item.
	 */
	@Override
	public void addProductGroup() {
		getView().displayAddProductGroupView();
		productContainerSelectionChanged();
	}
	
	/**
	 * Returns true if and only if the "Edit Product Group" menu item should be enabled.
	 */
	@Override
	public boolean canEditProductGroup() {
		return true;
	}
	
	/**
	 * This method is called when the user selects the "Edit Product Group" menu item.
	 */
	@Override
	public void editProductGroup() {
		getView().displayEditProductGroupView();
	}

	/**
	 * Returns true if and only if the "Delete Product Group" menu item should be enabled.
	 */
	@Override
	public boolean canDeleteProductGroup() {
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		
		if (selectedContainer == null ||
			selectedContainer.getTag() == null ||
			!(selectedContainer.getTag() instanceof ProductGroup))
			return false;
		
		ProductGroup productGroup = (ProductGroup)selectedContainer.getTag();
		
		return treeFacade.CanRemoveProductGroup(productGroup);
	}

	/**
	 * This method is called when the user selects the "Delete Product Group" menu item.
	 */
	@Override
	public void deleteProductGroup() {
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		
		if (selectedContainer == null ||
			selectedContainer.getTag() == null ||
			!(selectedContainer.getTag() instanceof ProductGroup))
			getView().displayErrorMessage("Invalid Product Group");
		
		ProductGroup productGroup = (ProductGroup)selectedContainer.getTag();
		
		treeFacade.DeleteProductGroup(productGroup);
		productContainerSelectionChanged();
		
		DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
		dao.StartTransaction();
		
		dao.GetProductGroupDAO().Remove(productGroup);
		dao.EndTransaction(true);
		dao.CloseConnection();
	}

	
//---------------------------------------------------------------------------------------
	//Product Methods
//---------------------------------------------------------------------------------------
	/**
	 * This method is called when the user drags a product into a
	 * product container.
	 * 
	 * @param productData Product dragged into the target product container
	 * @param containerData Target product container
	 */
	@Override
	public void addProductToContainer(ProductData productData, 
										ProductContainerData containerData) {
		
		try{
			
			Product product = (Product)productData.getTag();
			ProductContainer container = (ProductContainer)containerData.getTag();
			
			productFacade.TransferProduct((Product)productData.getTag(), 
				(ProductContainer)containerData.getTag());
			
			Iterator<Item> itemIter = itemFacade.GetItemsByProduct(container, product);
			
			DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
			dao.StartTransaction();
			
			dao.GetProductDAO().Update(product);
			dao.EndTransaction(true);
			
			while (itemIter.hasNext())
			{
				Item i = itemIter.next();
				dao.GetItemDAO().Update(i);
				dao.EndTransaction(true);
			}
			
			dao.CloseConnection();
			
			
		}
		catch (IllegalArgumentException e){
			getView().displayErrorMessage("Error while transferring product.");
		}
	}
	
	/**
	 * Returns true if and only if the "Edit Product" menu item should be enabled.
	 */
	@Override
	public boolean canEditProduct() {
		ProductData selectedProduct = getView().getSelectedProduct();
		
		return (selectedProduct != null && selectedProduct.getTag() != null);
	}
	
	/**
	 * This method is called when the user selects the "Edit Product" menu item.
	 */
	@Override
	public void editProduct() {
		getView().displayEditProductView();
	}

	/**
	 * Returns true if and only if the "Delete Product" menu item should be enabled.
	 */
	@Override
	public boolean canDeleteProduct() {
		
		ProductData selectedProduct = getView().getSelectedProduct();
		
		if (selectedProduct == null ||
				selectedProduct.getTag() == null)
			return false;
		
		Product product = (Product)selectedProduct.getTag();
		
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		
		if (selectedContainer == null || selectedContainer.getTag() == null) {
			
			// Root Node. Check to delete from system.
			return productFacade.CanRemoveProduct(product);
		}
		else {
			// In a container. Check to remove from Container.
			ProductContainer container = (ProductContainer)selectedContainer.getTag();
			return productFacade.CanRemoveProductFromContainer(product, container);
		}
	}

	/**
	 * This method is called when the user selects the "Delete Product" menu item.
	 */
	@Override
	public void deleteProduct() {
		ProductData selectedProduct = getView().getSelectedProduct();
		
		if (selectedProduct == null ||
				selectedProduct.getTag() == null)
			getView().displayErrorMessage("Invalid Product");
		
		Product product = (Product)selectedProduct.getTag();
		
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		
		if (selectedContainer == null || selectedContainer.getTag() == null) {
			
			// Root Node. Delete from system.
			productFacade.RemoveProduct(product);
			
			DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
			dao.StartTransaction();
			
			dao.GetProductDAO().Remove(product);
			dao.EndTransaction(true);
			dao.CloseConnection();
		}
		else {
			// In a container. Check to remove from Container.
			ProductContainer container = (ProductContainer)selectedContainer.getTag();
			productFacade.RemoveProductFromContainer(product, container);
			
			DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
			dao.StartTransaction();
			
			dao.GetProductDAO().Update(product);
			dao.EndTransaction(true);
			dao.CloseConnection();
		}
	}

	
//---------------------------------------------------------------------------------------
	//Item Methods
//---------------------------------------------------------------------------------------
	/**
	 * Loads all Items in the hit associated with the Product
	 * @param product the Product to search for
	 * @return List of Items
	 * */
	private List<Item> LoadItems(Product product) {
		List<Item> items = new ArrayList<Item>();
		
		//loop through and get all the items for the product
		Iterator<Item> i = itemFacade.GetItemsByProduct(null, product);
		while (i.hasNext()) {
			items.add(i.next());
		}
		
		return items;
	}
	
	/**
	 * Returns true if and only if the "Add Items" menu item should be enabled.
	 */
	@Override
	public boolean canAddItems() {
		return true;
	}

	/**
	 * This method is called when the user selects the "Add Items" menu item.
	 */
	@Override
	public void addItems() {
		getView().displayAddItemBatchView();
	}
	
	/**
	 * Returns true if and only if the "Transfer Items" menu item should be enabled.
	 */
	@Override
	public boolean canTransferItems() {
		return true;
	}
	
	/**
	 * This method is called when the user selects the "Transfer Items" menu item.
	 */
	@Override
	public void transferItems() {
		getView().displayTransferItemBatchView();
	}
	

	/**
	 * This method is called when the user drags an item into
	 * a product container.
	 * 
	 * @param itemData Item dragged into the target product container
	 * @param containerData Target product container
	 */
	@Override
	public void moveItemToContainer(ItemData itemData,
									ProductContainerData containerData) {
		try{
			List<Item> items = itemFacade.TransferItem(
					itemFacade.GetItemByBarcode(itemData.getBarcode()),
					(ProductContainer)containerData.getTag());
						
			DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
			dao.StartTransaction();
			
			for (Item item: items)
				dao.GetItemDAO().Update(item);
			dao.EndTransaction(true);
			
			dao.GetProductDAO().Update(items.get(0).GetProduct());
			dao.EndTransaction(true);
			
			dao.CloseConnection();
		}
		catch (IllegalArgumentException e){
			getView().displayErrorMessage("Error while transferring item.");
		}
	}

	
	/**
	 * Returns true if and only if the "Edit Item" menu item should be enabled.
	 */
	@Override
	public boolean canEditItem() {
		
		ItemData selectedItem = getView().getSelectedItem();
		
		return (selectedItem != null && selectedItem.getTag() != null);
	}

	/**
	 * This method is called when the user selects the "Edit Item" menu item.
	 */
	@Override
	public void editItem() {
		getView().displayEditItemView();
	}

	/**
	 * Returns true if and only if the "Remove Item" menu item should be enabled.
	 */
	@Override
	public boolean canRemoveItem() {
		
		ItemData selectedItem = getView().getSelectedItem();
		return (selectedItem != null && selectedItem.getTag() != null);
	}

	/**
	 * This method is called when the user selects the "Remove Item" menu item.
	 */
	@Override
	public void removeItem() {
		
		ItemData selectedItem = getView().getSelectedItem();
		
		if (selectedItem == null ||
				selectedItem.getTag() == null)
			getView().displayErrorMessage("Invalid Item");
		
		Item item = (Item)selectedItem.getTag();
		itemFacade.RemoveItem(item);
		
		DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
		dao.StartTransaction();
		
		dao.GetItemDAO().Update(item);
		dao.EndTransaction(true);
		dao.CloseConnection();
	}
	
	/**
	 * This method is called when the user selects the "Remove Items" menu item.
	 */
	@Override
	public void removeItems() {
		getView().displayRemoveItemBatchView();
	}
	
	/**
	 * Returns true if and only if the "Remove Items" menu item should be enabled.
	 */
	@Override
	public boolean canRemoveItems() {
		return true;
	}

//---------------------------------------------------------------------------------------
	//Selection Methods
//---------------------------------------------------------------------------------------
	
	/**
	 * This method is called when the selected item container changes.
	 */
	@Override
	public void productContainerSelectionChanged() {

		ProductContainerData selectedContainerData = getView().getSelectedProductContainer();
		
		ProductContainer container = (ProductContainer)selectedContainerData.getTag();
		
		if (container == null)
		{
			// Root container selected. List all products.
			getView().setContextUnit("All");
			getView().setContextGroup("");
			getView().setContextSupply("");
		}
		else
		{
			StorageUnit storageUnit = container.GetRootContainer();
			
			getView().setContextUnit(storageUnit.GetName());
			
			if (container == storageUnit) {
				getView().setContextGroup("");
				getView().setContextSupply("");
				
			}
			else {
				getView().setContextGroup(container.GetName());
				
				Size threeMonth = ((ProductGroup)container).GetThreeMonth();
				if (threeMonth.GetAmount() == 0)
					getView().setContextSupply("");
				else
					getView().setContextSupply(threeMonth.toString());
			}
		}
					
		DrawProducts(container, false);
		getView().selectProduct(null);
		
		// Clear Items list.
		getView().setItems(new ItemData[0]);
	}

	/**
	 * This method is called when the selected item changes.
	 */
	@Override
	public void productSelectionChanged() {
		
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		ProductContainer container = (ProductContainer)selectedContainer.getTag();
		
		ProductData selectedProduct = getView().getSelectedProduct();
		Product product = (Product)selectedProduct.getTag();

		getView().selectItem(null);
		
		DrawItems(container, product);
	}
	
	/**
	 * This method is called when the selected item changes.
	 */
	@Override
	public void itemSelectionChanged() {
		return;
	}
	
//---------------------------------------------------------------------------------------
	//Drawing Methods
//---------------------------------------------------------------------------------------
	protected void DrawProducts(ProductContainer container, boolean keepSelected) {
		//we will sort the product datas by their descriptions
		List<ProductData> products = new ArrayList<ProductData>();
		
		Product prevSelected = null;
		ProductData nextSelected = null;
		
		if (getView().getSelectedProduct() != null && keepSelected) {
			prevSelected = (Product)getView().getSelectedProduct().getTag();
		}

		try {
			Iterator<Product> iter = productFacade.GetProductsByContainer(container);
			
			while (iter.hasNext()) {
				ProductData temp = DataConverter.TranslateProduct(iter.next()); 
				
				List<Item> items = LoadItems((Product) temp.getTag());
				
				//count up the number of items in the container
				int count = 0;
				for (Item item: items) {
					if (item.GetContainer() == container || container == null) {
						count++;
					}
				}
				
				temp.setCount("" + count);
				
				products.add(temp);
				
				if (temp.getTag() == prevSelected) {
					nextSelected = temp;
				}
			}
			
			//sort the list
			Collections.sort(products, new Comparator<ProductData>() {
					@Override
					public int compare(ProductData o1, ProductData o2) {
						return o1.getDescription().compareToIgnoreCase(o2.getDescription());
					}
			});
			ProductData[] temp = new ProductData[products.size()];
			getView().setProducts(products.toArray(temp));
		
			getView().getSelectedProduct();
			
			if (nextSelected != null) {
				getView().selectProduct(nextSelected);
			}
			
			getView().getSelectedProduct();
		}
		catch (Exception e) {
			getView().setProducts(new ProductData[0]);
		}
		
	}
	
	protected void DrawItems(ProductContainer container, Product product) {
		
		List<ItemData> itemsList = new ArrayList<ItemData>();
		
		Item prevSelected = null;
		ItemData nextSelected = null;
		
		if (getView().getSelectedItem() != null) {
			prevSelected = (Item)getView().getSelectedItem().getTag();
		}
		
		Iterator<Item> iter = itemFacade.GetItemsByProduct(container, product);
		
		while (iter.hasNext()){
			ItemData temp = DataConverter.TranslateItem(iter.next()); 
			itemsList.add(temp);
		
			if (temp.getTag() == prevSelected)
				nextSelected = temp;
		}
		
		//sort the items by their creation date
		Collections.sort(itemsList, new Comparator<ItemData>() {
			@Override
			public int compare(ItemData o1, ItemData o2) {
				if (o1.getEntryDate().after(o2.getEntryDate()))
					return 1;
				else if (o1.getEntryDate().before(o2.getEntryDate()))
					return -1;
				return 0;
			}
		});
		ItemData[] temp = new ItemData[itemsList.size()];
		getView().setItems(itemsList.toArray(temp));
		
		if (nextSelected != null)
			getView().selectItem(nextSelected);
	}
}

